// TODO: CODE REVIEW & CLEANUP!
//
// SocketServer.cs
//
// Implements the SocketServer class and related types.
//

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using HomeUX.Utilities;

namespace HomeUX.Networking
{

/// <summary>
/// Accepts TCP connections from clients.
/// </summary>
///
[DebuggerDisplay("SocketServer for port {_portNumber}")]
public class SocketServer : IDisposable
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private and Internal Fields
    //

	/// <summary>
	/// Locked when state of this object is accessed.
	/// </summary>
	internal object _lock = new object();

    /// <summary>
    /// The port used to accept incoming connections.
    /// </summary>
    int _portNumber;

    /// <summary>
    /// Used to invoke events, if not <n>null</n> and if <r>_workQueue</r> is not <n>null</n>.  See
	/// the <r>SocketServer.SocketServer</r> constructor for more information.
    /// </summary>
    ISynchronizeInvoke _invokeEventsUsing;

	/// <summary>
	/// Used to invoke events, if not <n>null</n>.
	/// </summary>
	WorkQueue _workQueue;

    /// <summary>
    /// The maximum length of the pending connections queue.
    /// </summary>
    int _backlog;

    /// <summary>
    /// The size of the buffer, in bytes, to allocate for each local endpoint that accepts
	/// connections.
    /// </summary>
    int _bufferSize;

    /// <summary>
    /// The sockets that are listening for connections from clients.  There is one socket for each
    /// local address that's being monitored.
    /// </summary>
    List<Socket> _listeners = new List<Socket>();

    /// <summary>
    /// The current connections to clients.
    /// </summary>
    List<SocketConnection> _connections = new List<SocketConnection>();

    ///////////////////////////////////////////////////////////////////////////////////////////////
	// Public Properties
	//

	/// <summary>
	/// Returns <n>true</n> if this <r>SocketServer</r> has been disposed of.
	/// </summary>
	public bool IsDisposed { get; set; }

    ///////////////////////////////////////////////////////////////////////////////////////////////
	// Public Events
    //
    // NOTE: These events will fire on a non-application thread, unless <_invokeEventsUsing> is
    // is non-null.
    //

    /// <summary>
    /// Fired when the connection to the server is established.  Do not add event sinks to this
	/// event after <r>Start</r> has been called.
    /// </summary>
    ///
    public event ConnectedEventDelegate Connected;

	/// <summary>
	/// Fired when the connection to the server is broken.  Do not add event sinks to this event
	/// after <r>Start</r> has been called.
	/// </summary>
	///
    public event DisconnectedEventDelegate Disconnected;

	/// <summary>
	/// Fired when data is received from a client.  Do not add event sinks to this event after
	/// <r>Start</r> has been called.
	/// </summary>
	///
    public event DataReceivedEventDelegate DataReceived;

    ///////////////////////////////////////////////////////////////////////////////////////////////
	// Public Methods
	//

    /// <summary>
    /// Initializes an instance of this class. 
    /// </summary>
    ///
    /// <param name="portNumber">The port used to accept incoming connections.</param>
    ///
    /// <param name="invokeEventsUsing">Specifies a way to fire events on a specific thread.
    ///     For example, this may be a <r>WorkQueue</r> to cause events to fire on the work
    ///     queue's event, or a Windows Forms <n>Form</n> object, to avoid the Windows Forms
    ///     "cross-thread operation not valid" error.  If <pr>invokeEventsUsing</pr> is
    ///     <n>null</n>, events are invoked on an arbitrary thread.</param>
    /// 
    /// <param name="backlog">The maximum length of the pending connections queue.</param>
    /// 
    /// <param name="bufferSize">The size of the buffer, in bytes, to allocate for each local
    ///     endpoint that accepts connections.</param>
    ///
	/// <remarks>
	/// If <pr>invokeEventsUsing</pr> is a <r>WorkQueue</r>, exception handling is improved
	/// compared to a generic <n>ISynchronizeInvoke</n> implementation.
	/// </remarks>
	///
    public SocketServer(int portNumber, ISynchronizeInvoke invokeEventsUsing, int backlog,
        int bufferSize)
    {
        // save state
        _portNumber = portNumber;
        _invokeEventsUsing = invokeEventsUsing;
		_workQueue = _invokeEventsUsing as WorkQueue;
        _backlog = backlog;
        _bufferSize = bufferSize;

        // if <invokeEventsUsing> implements IDisposedSource, arrange to get called back when that
        // happens, since, in that case, we any pending BeginInvoke calls will not complete
        var iDisposedSource = invokeEventsUsing as IDisposedSource;
        if (iDisposedSource != null)
            iDisposedSource.Disposed += OnInvokeEventsUsingDisposed;
    }

    /// <summary>
    /// Begins accepting incoming connections.
    /// </summary>
    /// 
    /// <exception cref="FatalException">
    /// The <r>SocketServer</r> could not be started.
    /// </exception>
    ///
    public void Start()
    {
        try
        {
            lock (_lock)
            {
                // do nothing if this SocketServer was disposed
                if (IsDisposed)
                    return;

                // create one socket in <_listeners> for each local endpoint (i.e. each form of each IP
                // address of this computer)
                IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
                var addresses = new HashSet<IPAddress>(ipHostInfo.AddressList);
                addresses.Add(IPAddress.Loopback); // listen on "localhost" too
                foreach (IPAddress ipAddress in addresses)
                {
                    IPEndPoint localEndPoint = new IPEndPoint(ipAddress, _portNumber);
                    Socket listener = new Socket(localEndPoint.AddressFamily, SocketType.Stream,
                        ProtocolType.Tcp);
                    listener.Bind(localEndPoint);
                    listener.Listen(_backlog);
                    _listeners.Add(listener);
                    listener.BeginAccept(null, _bufferSize, OnConnect, listener);
                }
            }
        }
        catch (Exception ex)
        {
            if (ex is SocketException)
                throw new FatalException(ex, "Failed to listen on port {0}", _portNumber);
            else
                throw;
        }
    }

    /// <summary>
    /// Disconnects all clients, and stops accepting new incoming connections.
    /// </summary>
    ///
    public void Dispose()
    {
		lock (_lock)
		{
			// do nothing if this SocketServer was already disposed
			if (IsDisposed)
				return;

			// close all connections; do this in reverse order to avoid unnecessarily moving
            // memory around
			for (int index = _connections.Count - 1; index >= 0; index--)
			{
				SocketConnection connection = _connections[index];
				Disconnect(connection);
			}

			// mark this SocketServer as being disposed
			IsDisposed = true;
		}
    }

    /// <summary>
    /// Returns a snapshot of the current connections to clients.
    /// </summary>
    ///
    /// <remarks>
    /// Due to the multi-threaded nature of <r>SocketServer</r>, it's possible that a
    /// <r>SocketConnection</r> item in the returned array is closed by the time the caller
    /// examines it.
    /// </remarks>
    ///
    public SocketConnection[] GetConnections()
    {
        lock (_lock)
        {
            return _connections.ToArray();
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Internal Methods
    //

    /// <summary>
    /// Disconnects a given <r>SocketConnection</r> from the connected client, if any.
    /// </summary>
    ///
    /// <param name="connection">The <r>SocketConnection</r> to disconnect.</param>
    ///
    /// <remarks>
    /// This method may be called after <r>Dispose</r> -- in fact, there are cases where this will
    /// be unavoidable.
    /// </remarks>
    ///
    internal void Disconnect(SocketConnection connection)
	{
        lock (_lock)
        {
			// do nothing if <connection> isn't currently connected
            if (connection._socket == null)
				return;

            // close the connection
			connection._socket.Shutdown(SocketShutdown.Both);
			connection._socket.Close();
			connection._socket = null;

            // remove <connection> from the list of connections
            _connections.Remove(connection);
        }

        // tell the application about the disconnection
        FireDisconnected(connection);
	}

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private Methods
    //

    /// <summary>
    /// Called when a client connects to this <r>SocketServer</r>.
    /// </summary>
    ///
    /// <param name="ar">The state of asynchronous <n>Socket.BeginAccept</n> operation.</param>
    ///
    void OnConnect(IAsyncResult ar)
    {
        // set <listener> to the socket that accepted the connection request
        Socket listener = (Socket)ar.AsyncState;

        // finish accepting the incoming connection, and set <connection> to a new SocketConnection
		// object containing information about the connection, and <bytesReceived> to the number of
		// bytes of data received
        SocketConnection connection;
        int bytesReceived;
        lock (_lock)
		{
			// do nothing if this SocketServer was disposed
			if (IsDisposed)
				return;

			// finish accepting the connection and initialize <connection>
			connection = new SocketConnection();
            connection.Server = this;
            try
            {
                connection._socket = listener.EndAccept(out connection._buffer, out bytesReceived, ar);
            }
            catch (SocketException)
            {
                return;
            }
            IPEndPoint endPoint = connection._socket.RemoteEndPoint as IPEndPoint;
            if (endPoint != null)
                connection.ClientAddress = endPoint.Address;

            // add <connection> to SocketServer's list of connections
    		_connections.Add(connection);
		}

		// note: Dispose() may execute on another thread at this point

        // tell the application about the new connection; this also gives the application an
        // opportunity to set <connection.State>
        FireConnected(connection);

        // tell the application about the data we received, and prepare to receive subsequent data
        FireDataReceivedAndBeginReceiveNext(connection, bytesReceived);

        if (!IsDisposed)
        {
            // listen for the next connection on <listener>
            listener.BeginAccept(null, _bufferSize, OnConnect, listener);
        }
    }

    /// <summary>
    /// Called when data is received from a client.
    /// </summary>
    ///
    /// <param name="ar">The state of asynchronous <n>Socket.BeginAccept</n> operation.</param>
    ///
    void OnReceive(IAsyncResult ar)
    {
		// set <connection> to the SocketConnection object that's tracking the state of
		// communication with this client
        var connection = (SocketConnection)ar.AsyncState;

        // finish receiving the data; set <bytesReceived> to the number of bytes of data received
        int bytesReceived;
        lock (_lock)
		{
			// do nothing if this SocketServer was disposed
			if (IsDisposed)
				return;

			// finish receiving the data
			SocketError errorCode;
			bytesReceived = connection._socket.EndReceive(ar, out errorCode);

			// close this connection if we reached the end of the data stream or if there was a
			// socket error
			if ((bytesReceived == 0) || (errorCode != SocketError.Success))
			{
				Disconnect(connection); // lock (_lock) not required
				return;
			}
		}

		// note: Dispose() may execute on another thread at this point

        // tell the application about the data we received, and prepare to receive subsequent data
        FireDataReceivedAndBeginReceiveNext(connection, bytesReceived);
    }

	/// <summary>
	/// Fires a <r>Connected</r> event.
	/// </summary>
	///
	/// <param name="connection">The connection to the client.</param>
	///
	/// <remarks>
	/// This method may be called after <r>Dispose</r> -- in fact, there are cases where this will
	/// be unavoidable.
	/// </remarks>
	///
	void FireConnected(SocketConnection connection)
	{
        if (Connected != null) // lock not req'd
		{
			if ((_invokeEventsUsing == null) || !_invokeEventsUsing.InvokeRequired)
			{
				// fire the event on the current thread
				Connected(connection);
			}
			else
			if (_workQueue != null)
			{
				_workQueue.InvokeAsync(delegate()
				{
					Connected(connection);
				});
			}
			else
			{
				// fire the event on an application-specified thread
                _invokeEventsUsing.BeginInvoke(Connected, new object[] { connection });
			}
		}
	}

	/// <summary>
	/// Fires a <r>Disconnected</r> event.
	/// </summary>
	///
	/// <remarks>
	/// This method may be called after <r>Dispose</r> -- in fact, there are cases where this will
	/// be unavoidable.
	/// </remarks>
	///
    void FireDisconnected(SocketConnection connection)
	{
        if (Disconnected != null)
		{
			if ((_invokeEventsUsing == null) || !_invokeEventsUsing.InvokeRequired)
			{
				// fire the event on the current thread
				Disconnected(connection);
			}
			else
			if (_workQueue != null)
			{
				_workQueue.InvokeAsync(delegate()
				{
					Disconnected(connection);
				});
			}
			else
			{
                // fire the event on an application-specified thread
                _invokeEventsUsing.BeginInvoke(Disconnected, new object[] { connection });
            }
		}
	}

	/// <summary>
	/// Fires a <r>DataReceived</r> event (if a non-zero amount of data was received) and prepares
    /// to receive more data from the client.
	/// </summary>
	///
	/// <param name="connection">The connection to the client.</param>
	///
	/// <param name="bytesReceived">The number of bytes received.</param>
	///
	/// <remarks>
	/// This method may be called after <r>Dispose</r> -- in fact, there are cases where this will
	/// be unavoidable.
	/// </remarks>
	///
    void FireDataReceivedAndBeginReceiveNext(SocketConnection connection, int bytesReceived)
	{
        // switch to the application-specified thread and execute
		// HelpFireDataReceivedAndBeginReceiveNext
		if ((_invokeEventsUsing == null) || !_invokeEventsUsing.InvokeRequired) // lock not req'd
		{
			// fire the event on the current thread
            HelpFireDataReceivedAndBeginReceiveNext(connection, bytesReceived);
		}
		else
		{
            // fire the event on an application-specified thread
			if (_workQueue != null)
            {
                _workQueue.InvokeAsync(delegate()
				{
					HelpFireDataReceivedAndBeginReceiveNext(connection, bytesReceived);
				});
            }
            else
            {
                _invokeEventsUsing.BeginInvoke(
                    (Action<SocketConnection, int>)HelpFireDataReceivedAndBeginReceiveNext,
                    new object[] { connection, bytesReceived }); // lock not req'd
            }
        }
	}

    /// <summary>
    /// Fires a <r>DataReceived</r> event (if a non-zero amount of data was received) and prepares
    /// to receive more data from the client.  This method must execute on the thread specified by
    /// <r>_invokeEventsUsing</r>, if any.
    /// </summary>
	///
	/// <param name="connection">The connection to the client.</param>
    ///
    /// <param name="bytesReceived">The number of bytes received.</param>
    ///
    void HelpFireDataReceivedAndBeginReceiveNext(SocketConnection connection, int bytesReceived)
    {
        // do nothing if this SocketServer was disposed
        if (IsDisposed) // lock (_lock) not required
            return;

		// Note that there's no lock (_lock) around the following code (because it's bad practice
		// to hold a lock during a potentially lengthy callback to the application) -- but what
		// happens if, for example, Dispose is called during the client callback and <connection>
		// is disconnected?  Answer: it's okay because <connection._buffer> is not deallocated in
		// this situation.  Also, note that we serialize calls to BeginReceive, so the buffer
		// can't be overwritten by data until the application returns from the callback.

        // if data was received, fire the DataReceived event, otherwise assume that the
        // connection ended before any data was sent
        if (bytesReceived > 0)
        {
            if (DataReceived != null)
                DataReceived(connection, connection._buffer, bytesReceived);
        }
        else
			Disconnect(connection);

        // if <connection._buffer> was allocated by Accept, it's possible it won't be the size
        // specified by <_bufferSize> -- let's make sure it's at least not smaller than that
        if (connection._buffer.Length < _bufferSize)
            connection._buffer = new byte[_bufferSize];

        // prepare to receive the next buffer of data
        lock (_lock)
        {
            if (connection.IsConnected)
            {
                SocketError errorCode;
                connection._socket.BeginReceive(connection._buffer, 0, connection._buffer.Length,
                    0, out errorCode, OnReceive, connection);
                if (errorCode != SocketError.Success)
                    Disconnect(connection); // lock (_lock) not required
            }
        }
    }

    /// <summary>
    /// Called when <r>_invokeEventsUsing</r> has been disposed of.  This means that any pending
    /// <n>ISynchronizeInvoke.BeginInvoke</n> calls will never complete.
    /// </summary>
    ///
    void OnInvokeEventsUsingDisposed()
    {
        Dispose();
    }
}

/// <summary>
/// The type of an event handler for an event which is fired when a connection to the server is
/// established.
/// </summary>
///
/// <param name="connection">The connection to the client.</param>
///
/// <remarks>
/// See the <r>SocketServer.Connected</r> event.
/// </remarks>
///
public delegate void ConnectedEventDelegate(SocketConnection connection);

/// <summary>
/// The type of an event handler for an event which is fired when a connection to the server is
/// broken.
/// </summary>
///
/// <param name="connection">The connection to the client.</param>
///
/// <remarks>
/// See the <r>SocketServer.Disconnected</r> event.
/// </remarks>
///
public delegate void DisconnectedEventDelegate(SocketConnection connection);

/// <summary>
/// The type of an event handler for an event which is fired when data is received from a client.
/// </summary>
///
/// <param name="connection">The connection to the client.</param>
///
/// <param name="buffer">The buffer containing the bytes received.</param>
/// 
/// <param name="bytesReceived">The number of bytes received.</param>
///
/// <remarks>
/// See the <r>SocketServer.DataReceived</r> event.
/// </remarks>
///
public delegate void DataReceivedEventDelegate(SocketConnection connection, byte[] buffer,
    int bytesReceived);

/// <summary>
/// A connection to a client of a <r>SocketServer</r>.
/// </summary>
///
[DebuggerDisplay("{GetDebuggerDisplay()}")]
public class SocketConnection
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Internal Fields
    //

    /// <summary>
    /// The socket connected to the client.
    /// </summary>
	internal Socket _socket;

    /// <summary>
    /// The buffer used to receive data from the client.
    /// </summary>
	internal byte[] _buffer;

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Properties
    //

    /// <summary>
    /// The <r>SocketServer</r> that created this <r>SocketConnection</r>.
    /// </summary>
    public SocketServer Server { get; internal set; }

    /// <summary>
    /// Arbitrary application-defined state associated with this connection.
    /// </summary>
    public object Tag { get; set; }

    /// <summary>
    /// Returns <n>true</n> if the client is still connected.
    /// </summary>
    /// 
    /// <remarks>
    /// Due to the multi-threaded nature of <r>SocketServer</r>, it's possible for
	/// <r>IsConnected</r> to be <n>false</n> even within an event callback such as
	/// <r>SocketServer.Connected</r> or <r>SocketServer.DataReceived</r>.  Furthermore, the
    /// value of <r>IsConnected</r> can change at any time.
    /// </remarks>
	///
    public bool IsConnected
    {
        get
        {
            lock (Server._lock)
            {
                return (_socket != null);
            }
        }
    }

    /// <summary>
    /// The IP address of the client.
    /// </summary>
	///
    public IPAddress ClientAddress { get; internal set; }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    /// <summary>
    /// Sends a range of bytes to the client.
    /// </summary>
    ///
    /// <param name="buffer">The buffer containing the bytes to send.</param>
    ///
    /// <param name="offset">The offset within <pr>buffer</pr> of the range of bytes to send.
    ///     </param>
    ///
    /// <param name="size">The number of bytes to send.</param>
    ///
    /// <returns>
	/// <n>true</n> if the operation began successfully.  If the client is disconnected (which may
	/// occur on another thread while this method is executing), the data to send is silently
	/// discarded and <n>false</n> is returned.
    /// </returns>
    ///
    public bool Send(byte[] buffer, int offset, int size)
    {
		// "lock (Server._lock)" is not required since shared state is not accessed except within
		// a nested method which is responsible for locking

		// set <bufferCopy> to a copy of the data to send, so we don't depend on <buffer> not
		// being modified during the asynchronous send operation
		var bufferCopy = new byte[size];
		Buffer.BlockCopy(buffer, offset, bufferCopy, 0, size);

		// send the data
		return SendBuffer(bufferCopy);
    }

    /// <summary>
    /// Sends a buffer to the client.  The caller must not modify the buffer after making this call.
    /// </summary>
    ///
    /// <param name="buffer">The buffer to send.</param>
    ///
    /// <returns>
	/// <n>true</n> if the operation began successfully.  If the client is disconnected (which may
	/// occur on another thread while this method is executing), the data to send is silently
	/// discarded and <n>false</n> is returned.
    /// </returns>
    ///
    /// <remarks>
    /// Unlike <r>Send</r>, which makes a copy of the bytes to send, <r>SendBuffer</r> transfers
    /// ownership of the buffer to <r>SocketServer</r>.  <r>SendBuffer</r> avoids a buffer copy.
    /// </remarks>
    ///
    public bool SendBuffer(byte[] buffer)
    {
        lock (Server._lock)
        {
            // do nothing if the client is disconnected
            if (_socket == null)
                return false;

			var args = new SocketAsyncEventArgs();
			args.SetBuffer(buffer, 0, buffer.Length);
			args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
			if (!_socket.SendAsync(args))
				OnSendCompleted(_socket, args);

			return true;
		}
    }

    /// <summary>
    /// Sends a list of buffers to the client.  The caller must not modify any of the buffers
    /// after making this call.
    /// </summary>
    ///
    /// <param name="bufferList">The buffers to send.</param>
    ///
    /// <remarks>
    /// Unlike <r>Send</r>, which makes a copy of the bytes to send, <r>SendBufferList</r>
    /// transfers ownership of the buffers to <r>SocketServer</r>.  <r>SendBufferList</r> avoids a
    /// buffer copy.
    /// </remarks>
    ///
    public bool SendBufferList(IList<ArraySegment<byte>> bufferList)
    {
        lock (Server._lock)
        {
            // do nothing if the client is disconnected
            if (_socket == null)
                return false;

			var args = new SocketAsyncEventArgs();
			args.BufferList = bufferList;
			args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);

			if (!_socket.SendAsync(args))
				OnSendCompleted(_socket, args);

			return true;
		}
    }

    /// <summary>
    /// Disconnects from the client, if <r>SocketServer</r> is currently connected.
    /// </summary>
    ///
    public void Disconnect()
    {
        lock (Server._lock)
        {
            Server.Disconnect(this);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private Methods
    //

    /// <summary>
    /// Called when an asynchronous send operation is complete.
    /// </summary>
    ///
    /// <param name="sender">The event sender.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void OnSendCompleted(object sender, SocketAsyncEventArgs e)
    {
        // currently we don't do anything here
        return;
    }

#if DEBUG
	/// <summary>
	/// Returns the string used by the "[DebuggerDisplay]" attribute.
	/// </summary>
	///
	object GetDebuggerDisplay()
	{
		return new EncapsulatedString("SocketConnection to client {0}{1}",
			ClientAddress, (IsConnected ? "" : " - DISCONNECTED"));
	}
#endif
}

}

